using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using LINQPad;
using LINQPad.Extensibility.DataContext;
using Xtensive.Orm;
using Xtensive.Orm.Configuration;
using TypeInfo = Xtensive.Orm.Model.TypeInfo;

namespace LINQPadDriverHelper
{
  public static class Helper
  {
    public static StringBuilder SQLLog;
    public static IConnectionInfo CxInfo;
    public static Dictionary<Key, Entity> EntitiesCache { get; set; }
    private static Domain Domain;
    public static Domain GetDomain()
    {
      return Domain ?? (Domain = BuildDomain());
    }
    private static Domain BuildDomain()
    {
      var driverConfig = new DriverConfig(CxInfo.DriverData);
      foreach (string a in driverConfig.Assemblies)
      {
        CurrentDomainOnAssemblyResolve(null, new ResolveEventArgs(AssemblyName.GetAssemblyName(a).FullName));
      }
      if (driverConfig.Mode == BuildMode.FromConfig)
      {
        string domainName = driverConfig.ConfigDomainName;
        DomainConfiguration config = DomainConfiguration.Load(domainName);
        return Domain.Build(config);
      }
      string assembly = driverConfig.FactoryAssembly;
      string type = driverConfig.FactoryType;
      string method = driverConfig.FactoryMehod;
      MethodInfo methodInfo =
        AppDomain.CurrentDomain.GetAssemblies().Single(a => a.FullName == assembly).GetType(type).GetMethod(method);
      return ((Domain)methodInfo.Invoke(null, new object[0]));
    }

    public static ResolveEventHandler LINQPadAssemblyResolveHandler;

    public static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
    {
      if (CxInfo == null)
        return null;
      var name = new AssemblyName(args.Name);
      Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == name.Name);
      if (assembly != null)
        return assembly;
      string path = FindAssembly(name.Name);
      Assembly result = path == null ? null : Assembly.Load(File.ReadAllBytes(path));
      if (result == null && LINQPadAssemblyResolveHandler != null)
        return LINQPadAssemblyResolveHandler(sender, args);
      return result;
    }

    public static string FindAssembly(string name)
    {
      foreach (string assembly in Assemblies())
      {
        string path = Path.Combine(Path.GetDirectoryName(assembly), name + ".dll");
        if (File.Exists(path))
          return path;
        path = Path.Combine(Path.GetDirectoryName(assembly), name + ".exe");
        if (File.Exists(path))
          return path;
      }
      return null;
    }

    public static IEnumerable<string> Assemblies()
    {
      if (CxInfo.DriverData.Element("Assemblies") != null)
      {
        string[] files = ((string)CxInfo.DriverData.Element("Assemblies")).Split(Path.PathSeparator);
        foreach (string file in files)
        {
          yield return file;
        }
      }
    }

    private static readonly MethodInfo WrapForGridMethod = typeof(Helper).GetMethod("WrapForGrid");
    private static readonly MethodInfo WrapSingleForGridMethod = typeof(Helper).GetMethod("WrapSingleForGrid");
    public static object DisplayObjectInGrid(object objectToDisplay)
    {
      if (objectToDisplay is IEnumerable<Entity>)
      {
        Type i = objectToDisplay.GetType().GetInterface("IEnumerable`1");
        Type[] types = i.GetGenericArguments();
        Assembly assembly = DataContextDriver.LoadAssemblySafely(((AssemblyName)CxInfo.SessionData["assembly"]).CodeBase);
        var typeInfo = Session.Demand().Domain.Model.Types.Single(a => a.UnderlyingType == types[0]);
        Type type = assembly.GetType("LINQPadDriverHelper.Wrappers." + typeInfo.Name + "Wrapper");
        objectToDisplay = WrapForGridMethod.MakeGenericMethod(type).Invoke(null, new[] { objectToDisplay });
      }
      if (objectToDisplay is Entity)
      {
        Assembly assembly = DataContextDriver.LoadAssemblySafely(((AssemblyName)CxInfo.SessionData["assembly"]).CodeBase);
        TypeInfo typeInfo = Session.Demand().Domain.Model.Types.Single(a => a.UnderlyingType == objectToDisplay.GetType());
        Type type = assembly.GetType("LINQPadDriverHelper.Wrappers." + typeInfo.Name + "Wrapper");
        objectToDisplay = WrapSingleForGridMethod.MakeGenericMethod(type).Invoke(null, new[] { objectToDisplay });
      }
      return objectToDisplay;
    }

    public static ICustomMemberProvider CustomDisplayMemberProvider(object objectToWrite)
    {
      var toWrite = objectToWrite as Persistent;
      if (toWrite != null)
        return new PersistentMemberProvider(toWrite);
      return null;
    }
    // ReSharper disable once UnusedMember.Global
    public static IEnumerable<T> WrapForGrid<T>(IEnumerable<Entity> enumerable) where T : GridWrapper, new()
    {
      return enumerable.Select(a => DataContext.Attach<T>(a));
    }
    // ReSharper disable once UnusedMember.Global
    public static T WrapSingleForGrid<T>(Entity entity) where T : GridWrapper, new()
    {
      return DataContext.Attach<T>(entity);
    }

    public static void OnQueryFinishing(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager)
    {
      CxInfo = cxInfo;
      var session = (ISession)context;
      session.SaveChanges();
      EntitiesCache = null;

      executionManager.SqlTranslationWriter.Write(SQLLog);
    }

    public static void CleanCache()
    {
      EntitiesCache = null;
    }
    public static TValue GetOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
    {
      TValue value;
      dictionary.TryGetValue(key, out value);
      return value;
    }
    public static DataContextHack DataContext { get; set; }


  }
}